﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using PloobsEngine.Utils;
using PloobsEngine.Modelo;
using PloobsEngine.SceneControl;
using PloobsEngine.Cameras;
using PloobsEngine.Modelo.Animation;
using PloobsEngine.Physics.Bepu;

namespace PloobsEngine.Material
{
    public enum InstanceTechnich
    {
        COLOR,TEXTURE
    }

    public struct InstanceInfo
    {
        public Matrix World;
        public Vector4 Color;
    };

    public class InstancingDinamicNormalDeferred : IShader
    {
        private Effect _shader;
        VertexBuffer geometryBuffer;
        IndexBuffer indexBuffer;
        VertexDeclaration vertexDeclaration;
        VertexElement[] streamElements;
        int count = 10;
        DynamicVertexBuffer instanceBuffer;
        InstanceInfo[] instances;        
        InstanceTechnich tech = InstanceTechnich.COLOR;
        bool setUpTransforms;
        bool usePhysicTransforms;
        AgregatedPhysicObject agregated;

        public InstanceInfo[] Instances
        {
            get { return instances; }            
        }

        


        public InstancingDinamicNormalDeferred(InstanceTechnich tech, Matrix[] matrices, Color[] colors)
        {            
            this.tech = tech;
            this.count = matrices.Count();            
            usePhysicTransforms = false;            
            setUpTransforms = true;

            instances = new InstanceInfo[count];
            Random rnd = new Random();

            if (matrices.Count() != count || (colors != null && colors.Count() != count))
               throw new Exception("matrices/colors size must be the same size of count");

            for (int i = 0; i < count; i++)
            {
                instances[i].World = matrices[i];
                if (colors == null)
                {
                    instances[i].Color = new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), 1);
                }
                else
                {
                    instances[i].Color = colors[i].ToVector4(); ;
                }
            }

        }

        public InstancingDinamicNormalDeferred(InstanceTechnich tech, AgregatedPhysicObject agregated, Color[] colors)
        {            
            this.tech = tech;
            this.count = agregated.PhysicsObjects.Count;
            setUpTransforms = true;
            this.agregated = agregated;
            usePhysicTransforms = true;
            instances = new InstanceInfo[count];
        }

        private float specularIntensity = 0f;    
        public float SpecularIntensity
        {
            get { return specularIntensity; }
            set { specularIntensity = value; }
        }
        private float specularPower = 0f;

        public float SpecularPower
        {
            get { return specularPower; }
            set { specularPower = value; }
        }

        public override void Update(IObject ent, IList<PloobsEngine.Light.ILight> lights)
        {
            base.Update(ent, lights);
            if (usePhysicTransforms)
            {
                Matrix[] m = new Matrix[count];

                for (int i = 0; i < count; i++)
                {
                    m[i] = agregated.PhysicsObjects[i].WorldMatrix;
                }
                ChangeDynamicVertexBuffer(m);
            }
        }

        public override void Draw(IModelo modelo, IRenderHelper render, ICamera camera)
        {
            if (obj != null && instances!= null)
            {
                EngineStuff.GraphicsDevice.VertexDeclaration = vertexDeclaration;
                this._shader.Parameters["Texture"].SetValue(modelo.getTexture(TextureType.DIFFUSE));
                this._shader.Parameters["id"].SetValue(shaderId);
                this._shader.Parameters["specularIntensity"].SetValue(specularIntensity);
                this._shader.Parameters["specularPower"].SetValue(specularPower);


                Matrix[] boneTransforms = modelo.getBonesTransformation();                
                for (int i = 0; i < modelo.MeshNumber; i++)
                {
                    BatchInformation[] bi = modelo.GetBatchInformation(i);                
                    this._shader.Parameters["WVP"].SetValue(boneTransforms[modelo.GetParentBoneIndex(i)] * camera.View * camera.Projection);                   
                    for (int j = 0; j < bi.Count(); j++)
                    {
                        this._shader.Begin();
                        this._shader.CurrentTechnique.Passes[0].Begin();
                        EngineStuff.GraphicsDevice.Vertices[0].SetSource(modelo.GetVertexBuffer(i), bi[j].StreamOffset, bi[j].VertexStride);
                        EngineStuff.GraphicsDevice.Indices = modelo.GetIndexBuffer(i);
                        EngineStuff.GraphicsDevice.Vertices[0].SetFrequencyOfIndexData(count);
                        EngineStuff.GraphicsDevice.Vertices[1].SetSource(instanceBuffer, 0, sizeof(float) * (16 + 4));
                        EngineStuff.GraphicsDevice.Vertices[1].SetFrequencyOfInstanceData(1);
                        EngineStuff.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, bi[j].BaseVertex, 0, bi[j].NumVertices, bi[j].StartIndex, bi[j].PrimitiveCount);
                        this._shader.CurrentTechnique.Passes[0].End();
                        this._shader.End();
                     
                    }
                }
                EngineStuff.GraphicsDevice.Vertices[1].SetFrequencyOfInstanceData(1);
                EngineStuff.GraphicsDevice.Vertices[0].SetFrequencyOfIndexData(1);
            }
            
        }
        public override void Initialize()
        {
            base.Initialize();
            this._shader = EngineStuff.InternalContentManager.GetAsset<Effect>("InstanceRenderGBuffer");
            switch (tech)
            {
                case InstanceTechnich.COLOR:
                    this._shader.CurrentTechnique = this._shader.Techniques["TechniqueColor"];
                    break;
                case InstanceTechnich.TEXTURE:
                    this._shader.CurrentTechnique = this._shader.Techniques["TechniqueTexture"];
                    break;                
            }

            instanceBuffer = new DynamicVertexBuffer(EngineStuff.GraphicsDevice, sizeof(float) * (16 + 4) * count, BufferUsage.WriteOnly);

        }

        public override void PreUpdate(IObject ent, IList<PloobsEngine.Light.ILight> lights)
        {            

            base.PreUpdate(ent, lights);
            GenerateBuffers(ent);
            GenerateStreamElements(ent);
            if (setUpTransforms == false)
            {
                GenerateInstanceInformation(ent);
            }
            else
            {     
                if(instances!=null)
                    instanceBuffer.SetData(instances, 0, instances.Count(), SetDataOptions.Discard);   
            }
        }

        public override MaterialType MaterialType
        {
            get { return MaterialType.DEFERRED; }
        }


        private void GenerateBuffers(IObject ent)
        {            
            geometryBuffer = ent.Modelo.GetVertexBuffer(0);
            indexBuffer = ent.Modelo.GetIndexBuffer(0);            
        }

        private void GenerateStreamElements(IObject ent)
        {
            streamElements = new VertexElement[8];
            streamElements[0] = new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0);
            streamElements[1] = new VertexElement(0, 12, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0);
            streamElements[2] = new VertexElement(0, 24, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0);            

            streamElements[3] = new VertexElement(1, 0                , VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 1);
            streamElements[4] = new VertexElement(1, sizeof(float) * 4, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 2);
            streamElements[5] = new VertexElement(1, sizeof(float) * 8, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 3);
            streamElements[6] = new VertexElement(1, sizeof(float) * 12, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate,4);
            streamElements[7] = new VertexElement(1, sizeof(float) * 16, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 5);
            vertexDeclaration = new VertexDeclaration(EngineStuff.GraphicsDevice, streamElements);
        }

        private void GenerateInstanceInformation(IObject ent)
        {
            instances = new InstanceInfo[count];
            Random rnd = new Random();
            float dist = 150f;
            for (int i = 0; i < count; i++)
            {
                instances[i].World = Matrix.CreateTranslation(new Vector3(i * dist,i * dist, i * dist));
                instances[i].Color = new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), 1);
            }

            instanceBuffer = new DynamicVertexBuffer(EngineStuff.GraphicsDevice, sizeof(float) * (16 + 4) * count, BufferUsage.WriteOnly);
            instanceBuffer.SetData(instances,0,instances.Count(),SetDataOptions.Discard);            
        }

        public void ChangeDynamicVertexBuffer(Matrix[] worldsMatrix)
        {
            if (instanceBuffer == null)
                return;

            if (worldsMatrix.Count() != count)
            {
                throw new Exception("invalid number of matrices");
            }
            
            for (int i = 0; i < count; i++)
            {
                instances[i].World = worldsMatrix[i];
            }
            
            instanceBuffer.SetData(instances, 0, instances.Count(), SetDataOptions.Discard);            

        }

        public void ChangeDynamicVertexBuffer(Color[] color)
        {
            if (color.Count() != count)
            {
                throw new Exception("invalid number of colors");
            }

            for (int i = 0; i < count; i++)
            {
                instances[i].Color = color[i].ToVector4();
            }

            instanceBuffer.SetData(instances, 0, instances.Count(), SetDataOptions.Discard);

        }

        public void ChangeDynamicVertexBuffer(InstanceInfo[] instance)
        {
            if (instance.Count() != count)
            {
                throw new Exception("invalid number of instances");
            }

            this.instances = instance;
            instanceBuffer.SetData(instances, 0, instances.Count(), SetDataOptions.Discard);

        }

        public void ChangeDynamicVertexBuffer(Matrix[] worldsMatrix,Color[] color)
        {
            if (color.Count() != count || worldsMatrix.Count() != count)
            {
                throw new Exception("invalid number of colors/matrices");
            }

            for (int i = 0; i < count; i++)
            {
                instances[i].Color = color[i].ToVector4();
                instances[i].World = worldsMatrix[i];
            }

            instanceBuffer.SetData(instances, 0, instances.Count(), SetDataOptions.Discard);

        }

    }
}
